﻿#region Using

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Groups;
using Vk.Methods.Users;

#endregion

namespace Vk.Methods.Audios
{
    /// <summary>
    ///     Аудиозаписи
    /// </summary>
    public class Audios
    {
        private readonly Api _api;

        public Audios(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Копирует аудиозапись на страницу пользователя или группы.
        /// </summary>
        /// <param name="audioId">ID аудиозаписи.</param>
        /// <param name="ownerId">
        ///     ID владельца аудиозаписи. Если копируемая аудиозапись находится на странице группы, в этом
        ///     параметре должно стоять значение, равное -id группы.
        /// </param>
        /// <param name="groupId">
        ///     ID группы, в которую следует копировать аудиозапись. Если параметр не указан, аудиозапись
        ///     копируется не в группу, а на страницу текущего пользователя. Если аудиозапись все же копируется в группу, у
        ///     текущего пользователя должны быть права на эту операцию.
        /// </param>
        /// <param name="albumId">Идентификатор альбома, в который нужно переместить аудиозапись. </param>
        public async Task<bool> Add(int audioId, int ownerId, int? groupId = null, int? albumId = null)
        {
            var query = new ApiQuery("audio.add")
                .Add("audio_id", audioId)
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int() > 0;
        }

        /// <summary>
        ///     Создает пустой альбом аудиозаписей.
        /// </summary>
        /// <param name="title">Название альбома.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат аудиозаписи. Если параметр не указан, то альбом создается у текущего
        ///     пользователя.
        /// </param>
        /// <returns>Возвращает album_id - id созданного альбома. </returns>
        public async Task<int> AddAlbum(string title, int? groupId)
        {
            var query = new ApiQuery("audio.addAlbum")
                .Add("title", title)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Удаляет аудиозапись со страницы пользователя или группы.
        /// </summary>
        /// <param name="audioId">ID аудиозаписи.</param>
        /// <param name="ownerId">
        ///     ID владельца аудиозаписи. Если удаляемая аудиозапись находится на странице группы, в этом
        ///     параметре должно стоять значение, равное -id группы.
        /// </param>
        public async Task<bool> Delete(int audioId, int ownerId)
        {
            var query = new ApiQuery("audio.delete")
                .Add("audio_id", audioId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет альбом аудиозаписей.
        /// </summary>
        /// <param name="albumId">ID удаляемого альбома.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат аудиозаписи. Если параметр не указан, то альбом удаляется у текущего
        ///     пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> DeleteAlbum(int albumId, int? groupId)
        {
            var query = new ApiQuery("audio.deleteAlbum")
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует данные аудиозаписи на странице пользователя или группы.
        /// </summary>
        /// <param name="audioId">ID аудиозаписи.</param>
        /// <param name="ownerId">
        ///     ID владельца аудиозаписи. Если редактируемая аудиозапись находится на странице группы, в этом
        ///     параметре должно стоять значение, равное -id группы.
        /// </param>
        /// <param name="artist">Название исполнителя аудиозаписи.</param>
        /// <param name="title">Название аудиозаписи.</param>
        /// <param name="text">Текст аудиозаписи, если введен.</param>
        /// <param name="noSearch">true - скрывает аудиозапись из поиска по аудиозаписям, false (по умолчанию) - не скрывает.</param>
        /// <param name="genreId">Идентификатор жанра из списка аудио жанров. </param>
        /// <returns>
        ///     После успешного выполнения возвращает id текста, введенного пользователем (lyrics_id), если текст не был
        ///     введен, вернет 0.
        /// </returns>
        public async Task<int> Edit(int audioId,
                                    int ownerId,
                                    string artist,
                                    string title,
                                    string text,
                                    bool noSearch = false,
                                    int? genreId = null)
        {
            var query = new ApiQuery("audio.edit")
                .Add("audio_id", audioId)
                .Add("owner_id", ownerId)
                .Add("artist", artist)
                .Add("title", title)
                .Add("text", text)
                .Add("no_search", noSearch)
                .Add("genre_id", genreId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Редактирует название альбома аудиозаписей.
        /// </summary>
        /// <param name="title">Новое название альбома.</param>
        /// <param name="albumId">ID редактируемого альбома.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат аудиозаписи. Если параметр не указан, то изменяется альбом текущего
        ///     пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> EditAlbum(string title, int albumId, int? groupId)
        {
            var query = new ApiQuery("audio.editAlbum")
                .Add("album_id", albumId)
                .Add("title", title)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает список аудиозаписей пользователя или группы.
        /// </summary>
        /// <param name="ownerId">ID пользователя, которому принадлежат аудиозаписи (по умолчанию — текущий пользователь)</param>
        /// <param name="albumId">
        ///     ID альбома, аудиозаписи которого необходимо вернуть (по умолчанию возвращаются аудиозаписи из
        ///     всех альбомов).
        /// </param>
        /// <param name="audioIds">Перечисленные через запятую id аудиозаписей, входящие в выборку по userId или gid.</param>
        /// <param name="count">Количество возвращаемых аудиозаписей.</param>
        /// <param name="offset">Смещение относительно первой найденной аудиозаписи для выборки определенного подмножества.</param>
        public async Task<ListCount<Audio>> Get(int? ownerId = null,
                                                int? albumId = null,
                                                IEnumerable<string> audioIds = null,
                                                int? count = null,
                                                int? offset = null)
        {
            var query = new ApiQuery("audio.get")
                .Add("owner_id", ownerId)
                .Add("audio_ids", audioIds)
                .Add("album_id", albumId)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);
            if (!_api.Manager.MethodSuccessed)
                return null;

            return new ListCount<Audio>(result.Int("count").Value,
                                        (from ApiNode node in result.SelectNodes("items/*") select new Audio(node))
                                            .ToList());
        }

        /// <summary>
        ///     Возвращает список альбомов аудиозаписей пользователя или группы.
        /// </summary>
        /// <param name="ownerId">
        ///     идентификатор пользователя или сообщества, у которого необходимо получить список альбомов с
        ///     аудио.
        /// </param>
        /// <param name="count">Количество альбомов, которое необходимо вернуть. (по умолчанию – не больше 50, максимум - 100).</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества альбомов.</param>
        public async Task<ListCount<AudioAlbum>> GetAlbums(int? ownerId = null, int? count = null, int? offset = null)
        {
            var query = new ApiQuery("audio.getAlbums")
                .Add("owner_id", ownerId)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);
            var nodes = result.SelectNodes("items/*");
            return _api.Manager.MethodSuccessed
                       ? new ListCount<AudioAlbum>(result.Int("count").Value,
                                                   nodes.Select(x => new AudioAlbum(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список друзей и сообществ пользователя, которые транслируют музыку в статус.
        /// </summary>
        /// <param name="filter">Определяет, какие типы объектов необходимо получить</param>
        /// <param name="active">true — будут возвращены только друзья и сообщества, которые транслируют музыку в данный момент.</param>
        public async Task<UsersGroupsList> GetBroadcastList(BroadcastListFilter filter = null, bool? active = null)
        {
            var query = new ApiQuery("audio.getBroadcastList")
                .Add("filter", filter)
                .Add("active", active);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                var res = new UsersGroupsList();
                var userNodes = result.ChildNodes.Where(x => x.String("type") == "profile");
                res.Users = userNodes.Select(x => new BaseUser(x)).ToList();
                var groupNodes = result.ChildNodes.Where(x => x.String("type") == "page");
                res.Groups = groupNodes.Select(x => new Group(x)).ToList();
                return res;
            }
            return null;
        }

        /// <summary>
        ///     Возвращает информацию об аудиозаписях.
        /// </summary>
        /// <param name="audios">
        ///     Перечисленные через запятую идентификаторы – идущие через знак подчеркивания id пользователей,
        ///     которым принадлежат аудиозаписи, и id самих аудиозаписей. Если аудиозапись принадлежит группе, то в качестве
        ///     первого параметра используется -id группы.
        /// </param>
        public async Task<List<Audio>> GetById(IEnumerable<string> audios)
        {
            var query = new ApiQuery("audio.getById")
                .Add("audios", audios);

            var result = await _api.Manager.Execute(query);
            if (!_api.Manager.MethodSuccessed)
                return null;

            var nodes = result.SelectNodes("audio");
            return (from ApiNode node in nodes select new Audio(node)).ToList();
        }

        /// <summary>
        ///     Возвращает количество аудиозаписей пользователя или группы.
        /// </summary>
        /// <param name="ownerId">
        ///     ID владельца аудиозаписей. Если необходимо получить количество аудиозаписей группы, в этом
        ///     параметре должно стоять значение, равное -id группы.
        /// </param>
        public async Task<int> GetCount(int ownerId)
        {
            var query = new ApiQuery("audio.getCount")
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Возвращает текст аудиозаписи по параметру lyrics_id, полученному в результате работы методов audio.get,
        ///     audio.getById или audio.search.
        /// </summary>
        /// <param name="lyricsId">ID текста аудиозаписи, полученный в audio.get, audio.getById или audio.search.</param>
        public async Task<string> GetLyrics(int lyricsId)
        {
            var query = new ApiQuery("audio.getLyrics")
                .Add("lyrics_id", lyricsId);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                var node = result.SelectNode("lyrics/text");
                if (node != null)
                    return node.String();
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список аудиозаписей из раздела "Популярное"
        /// </summary>
        /// <param name="onlyEng">
        ///     true – возвращать только зарубежные аудиозаписи. false – возвращать все аудиозаписи. (по
        ///     умолчанию)
        /// </param>
        /// <param name="genreId">Идентификатор жанра из списка жанров.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества аудиозаписей. </param>
        /// <param name="count">Количество возвращаемых аудиозаписей. </param>
        public async Task<List<Audio>> GetPopular(bool? onlyEng = null,
                                                  ApiEnum<AudioGenre> genreId = null,
                                                  int? offset = null,
                                                  int? count = null)
        {
            var query = new ApiQuery("audio.getPopular")
                .Add("only_eng", onlyEng)
                .Add("genre_id", genreId?.Value)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? result.ChildNodes.Select(x => new Audio(x)).ToList()
                       : null;
        }

        /// <summary>
        ///     Возвращает список рекомендуемых аудиозаписей на основе списка воспроизведения заданного пользователя или на основе
        ///     одной выбранной аудиозаписи.
        /// </summary>
        /// <param name="targetAudio">
        ///     Идентификатор аудиозаписи, на основе которой будет строиться список рекомендаций.
        ///     Используется вместо параметра userId. Идентификатор представляет из себя разделённые знаком подчеркивания id
        ///     пользователя, которому принадлежит аудиозапись, и id самой аудиозаписи. Если аудиозапись принадлежит сообществу, то
        ///     в качестве первого параметра используется -id сообщества.
        /// </param>
        /// <param name="userId">
        ///     Идентификатор пользователя для получения списка рекомендаций на основе его набора аудиозаписей (по
        ///     умолчанию — идентификатор текущего пользователя).
        /// </param>
        /// <param name="offset">Смещение относительно первой найденной аудиозаписи для выборки определенного подмножества.</param>
        /// <param name="count">Количество возвращаемых аудиозаписей. Максимальное значение — 200, по умолчанию — 100.</param>
        /// <param name="shuffle">true — включен случайный порядок.</param>
        public async Task<ListCount<Audio>> GetRecommendations(string targetAudio = null,
                                                               int? userId = null,
                                                               int? offset = null,
                                                               int? count = null,
                                                               bool? shuffle = null)
        {
            var query = new ApiQuery("audio.getRecommendations")
                .Add("user_id", userId)
                .Add("target_audio", targetAudio)
                .Add("count", count)
                .Add("offset", offset)
                .Add("shuffle", shuffle);

            var result = await _api.Manager.Execute(query);
            var nodes = result.SelectNodes("items/audio");
            return _api.Manager.MethodSuccessed && nodes != null && nodes.Any()
                       ? new ListCount<Audio>(result.Int("count").Value, nodes.Select(x => new Audio(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки аудиозаписей.
        /// </summary>
        public async Task<string> GetUploadServer()
        {
            var query = new ApiQuery("audio.getUploadServer");

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return result.String("upload_url");
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список аудиозаписей пользователя или группы, дополнительно возвращается объект user.
        /// </summary>
        /// <param name="ownerId">ID пользователя, которому принадлежат аудиозаписи (по умолчанию — текущий пользователь)</param>
        /// <param name="albumId">
        ///     ID альбома, аудиозаписи которого необходимо вернуть (по умолчанию возвращаются аудиозаписи из
        ///     всех альбомов).
        /// </param>
        /// <param name="audioIds">Перечисленные через запятую id аудиозаписей, входящие в выборку по userId или gid.</param>
        /// <param name="count">Количество возвращаемых аудиозаписей.</param>
        /// <param name="offset">Смещение относительно первой найденной аудиозаписи для выборки определенного подмножества.</param>
        public async Task<GetWithUserInfoResult> GetWithUserInfo(int? ownerId = null,
                                                                 int? albumId = null,
                                                                 IEnumerable<string> audioIds = null,
                                                                 int? count = null,
                                                                 int? offset = null)
        {
            var query = new ApiQuery("audio.get")
                .Add("need_user", 1)
                .Add("owner_id", ownerId)
                .Add("audio_ids", audioIds)
                .Add("album_id", albumId)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new GetWithUserInfoResult(result) : null;
        }

        /// <summary>
        ///     Перемещает аудиозаписи в альбом.
        /// </summary>
        /// <param name="audioIds">ID аудиозаписей, перечисленные через запятую.</param>
        /// <param name="albumId">ID альбома, в который перемещаются аудиозаписи.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат аудиозаписи. Если параметр не указан, то работа ведется с альбомом
        ///     текущего пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> MoveToAlbum(IEnumerable<string> audioIds, int albumId, int? groupId)
        {
            var query = new ApiQuery("audio.moveToAlbum")
                .Add("audio_ids", audioIds)
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return result.Bool().Value;
        }

        /// <summary>
        ///     Изменяет порядок аудиозаписи, перенося ее между аудиозаписями, идентификаторы которых переданы параметрами after и
        ///     before.
        /// </summary>
        /// <param name="audioId">ID аудиозаписи, порядок которой изменяется.</param>
        /// <param name="before">
        ///     ID аудиозаписи, перед которой нужно поместить аудиозапись. Если аудиозапись переносится в конец,
        ///     параметр может быть равен нулю.
        /// </param>
        /// <param name="after">
        ///     ID аудиозаписи, после которой нужно поместить аудиозапись. Если аудиозапись переносится в начало,
        ///     параметр может быть равен нулю.
        /// </param>
        /// <param name="ownerId">ID владельца изменяемой аудиозаписи. По умолчанию - id текущего пользователя.</param>
        public async Task<bool> Reorder(int audioId, int before, int after, int? ownerId = null)
        {
            var query = new ApiQuery("audio.reorder")
                .Add("audio_id", audioId)
                .Add("before", before)
                .Add("after", after)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Восстанавливает удаленную аудиозапись пользователя после удаления.
        /// </summary>
        /// <param name="audioId">ID удаленной аудиозаписи.</param>
        /// <param name="ownerId">ID владельца аудиозаписи. По умолчанию - id текущего пользователя.</param>
        public async Task<bool> Restore(int audioId, int? ownerId = null)
        {
            var query = new ApiQuery("audio.restore")
                .Add("audio_id", audioId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Сохраняет аудиозаписи после успешной загрузки.
        /// </summary>
        /// <param name="server">Параметр, возвращаемый в результате загрузки аудиофайла на сервер.</param>
        /// <param name="audio">Параметр, возвращаемый в результате загрузки аудиофайла на сервер.</param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки аудиофайла на сервер.</param>
        /// <param name="title">Название композиции. По умолчанию берется из ID3 тегов.</param>
        /// <param name="artist">Автор композиции. По умолчанию берется из ID3 тегов.</param>
        public async Task<Audio> Save(string server,
                                      string audio,
                                      string hash,
                                      string title = null,
                                      string artist = null)
        {
            var query = new ApiQuery("audio.save")
                .Add("server", server)
                .Add("audio", audio)
                .Add("hash", hash)
                .Add("title", title)
                .Add("artist", artist);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Audio(result) : null;
        }

        /// <summary>
        ///     Возвращает список аудиозаписей в соответствии с заданным критерием поиска.
        /// </summary>
        /// <param name="q">Строка поискового запроса. Например, The Beatles.</param>
        /// <param name="autoComplete">
        ///     Если этот параметр равен true, возможные ошибки в поисковом запросе будут исправлены.
        ///     Например, при поисковом запросе Иуфдуы поиск будет осуществляться по строке Beatles.
        /// </param>
        /// <param name="sort">Вид сортировки. 2 - по популярности, 1 - по длительности аудиозаписи, 0 - по дате добавления.</param>
        /// <param name="lyrics">
        ///     Если этот параметр равен true, поиск будет производиться только по тем аудиозаписям, которые
        ///     содержат тексты.
        /// </param>
        /// <param name="count">Количество возвращаемых аудиозаписей (максимум 200).</param>
        /// <param name="offset">Смещение относительно первой найденной аудиозаписи для выборки определенного подмножества.</param>
        /// <param name="searchOwn">
        ///     true – искать по аудиозаписям пользователя, false – не искать по аудиозаписям пользователя (по
        ///     умолчанию).
        /// </param>
        /// <param name="performerOnly">Если этот параметр равен true, поиск будет осуществляться только по названию исполнителя. </param>
        public async Task<ListCount<Audio>> Search(string q,
                                                   bool? autoComplete = null,
                                                   AudioSortOrder sort = null,
                                                   bool? lyrics = null,
                                                   int? count = null,
                                                   int? offset = null,
                                                   bool? searchOwn = null,
                                                   bool? performerOnly = null)
        {
            var apiQuery = new ApiQuery("audio.search")
                .Add("q", q)
                .Add("sort", sort)
                .Add("lyrics", lyrics)
                .Add("auto_complete", autoComplete)
                .Add("count", count)
                .Add("offset", offset)
                .Add("search_own", searchOwn)
                .Add("performer_only", performerOnly);


            var result = await _api.Manager.Execute(apiQuery);
            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("items/audio");
                return new ListCount<Audio>(result.Int("count").Value,
                                            (from ApiNode node in nodes select new Audio(node)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Транслирует аудиозапись в статус пользователю или сообществу.
        /// </summary>
        /// <param name="audio">
        ///     Идентификатор аудиозаписи, которая будет отображаться в статусе, в формате owner_id+audio_id.
        ///     Например, 1_190442705. Если параметр не указан, аудиостатус указанных сообществ и пользователя будет удален.
        /// </param>
        /// <param name="targetIds">
        ///     Перечисленные через запятую идентификаторы сообществ и пользователя, которым будет
        ///     транслироваться аудиозапись. Идентификаторы сообществ должны быть заданы в формате "-gid", где gid - идентификатор
        ///     сообщества. Например, 1,-34384434. По умолчанию аудиозапись транслируется текущему пользователю.
        /// </param>
        /// <returns>
        ///     В случае успешного выполнения возвращает массив идентификаторов сообществ и пользователя, которым был
        ///     установлен или удален аудиостатус.
        /// </returns>
        public async Task<List<int>> SetBroadcast(string audio = null, IEnumerable<string> targetIds = null)
        {
            var query = new ApiQuery("audio.setBroadcast")
                .Add("audio", audio)
                .Add("target_ids", targetIds);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? result.ChildNodes.Select(x => x.Int()).Where(x => x.HasValue).Select(x => x.Value).ToList()
                       : null;
        }
    }
}